home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/gawk -f
- # exp: expense tabulator
- # @(#) exp.gawk 3.6 95/10/08
- # 1989, 1990 John H. DuBois III (john@armory.com)
- # 90/05/19 Ported from DOS to XENIX
- # 91/07/19 Accept multiple input files
- # 91/08/04 Made width of name field a variable; set to 9 chars
- # 92/09/10 Extensive cleanup, added line splitting, change to #!awk
- # script because gawk failed on it after cleanup
- # 92/12/09 Added help.
- # 93/06/25 Year may be included in dates.
- # 93/07/01 Added c option.
- # 93/07/02 Added use of HOUSELIB.
- # 94/04/24 Allow any separators except the one immediately before the
- # comment field to be spaces. Fixed Sep assignment.
- # 94/06/11 Do not report on those who have 0 balance and are not in All.
- # 94/09/20 Ignore blank lines.
- # 95/03/31 Detect recursive aliases. Ignore case in inmate names.
- # 95/04/15 Avoid timezone issues by working with days instead of seconds.
- # Make p option work again.
- # 95/06/19 Added OwedBy and OwedTo vars
- # 95/07/12 Added dptTR options.
- # 95/07/24 Better checking of aliases.
- # 95/08/26 Make carryover output work correctly. Reduced number of globals.
- # 95/09/30 Added P option.
- # 95/10/01 Changed d option to s; added eM options; fixed Inmates[] tests
- # 95/10/08 Added V option.
-
- BEGIN {
- Run()
- }
-
- # Globals:
- # DefaultSep is true if the default field separator is being used.
- # MinReptBal is the minimum report balance.
- # Debug is true if debugging is on.
- # FS is the input field separator.
- # FILENAME is the name of the file currently being processed.
- # FNR is the line number last read from that file.
- # FirstDate and LastDate are the first and last dates encountered.
- # VBalances[Party,dir-sign "," cost-sign] is used for the -V option, to
- # accumulate more detailed expense information. Balances are added to
- # VBalances[] just as they are added to Balances[], except that the absolute
- # value of the cost fracftion is added, and additional sign indexes are used.
- # dir-sign is 1 for expenses in which Party represents more
- # of the owed-by field than the owed-to field, and -1 for expenses in which
- # Party represents more of the owed-to field than than the owed by field.
- # cost-sign is 1 for expenses in which the cost was positive, and -1 for
- # expenses in which the cost was negative.
- function Run( Lib,DefExp,Options,Carryover,PrintRunning,RunningBalance,
- LastPayment,PartyListGiven,ProcParties,StartDate,EndDate,MaxName,ArgInd,ret) {
- Lib = "/usr/local/lib/house"
- DefExp = "expenses"
- MinReptBal = 0.02
- DefaultSep = 1
- if ((ARGC = ProcArgs(ARGC,ARGV,"bchrRltTxp:P:s:e:M(V",Options,1)) < 0) {
- print "exp: " OptErr
- exit(1)
- }
- if ("h" in Options) {
- printf \
- "exp: process an expenses file.\n"\
- "Usage: exp [-bchlrRtTV] [-p<party,...>] [-P<payment-types>] [-M<min-amt>]\n"\
- " [-s<start-date>] [-e<end-date>] [expenses-file ...]\n"\
- "If no expenses-file is given, the default filename \"%s\" is used. It is\n"\
- "expected to be found in the directory %s. If the environment\n"\
- "variable HOUSELIB is set, it is used instead of %s. See the\n"\
- "exp(LOCAL) man page for a description of the input file format. The only\n"\
- "users reported on are those who are currently in the \"All\" set and\n"\
- "those who have an outstanding balance whose absolute value is greater than\n"\
- "or equal to the minimum report balance, by default $%04.2f. This can be\n"\
- "changed with the -M option.\n"\
- "Options: See the exp(LOCAL) man page.\n",DefExp,Lib,Lib,MinReptBal
-
- exit(0)
- }
- # If MakePTypes fails, it return an error message, else null
- if ((PTypesGiven = "P" in Options) &&
- ((e = MakePTypes(Options["P"],PTypes)) != ""))
- ErrExit(e)
- if ("HOUSELIB" in ENVIRON)
- Lib = ENVIRON["HOUSELIB"]
- DefExp = Lib "/" DefExp
- Carryover = "c" in Options
- PrintRunning = "r" in Options || "t" in Options || "R" in Options ||
- "T" in Options
- RunningBalance = "R" in Options || "T" in Options
- LastPayment = "l" in Options
- if (PartyListGiven = ("p" in Options))
- NumPartiesGiven = MakeSet(ProcParties,tolower(Options["p"]),",")
- if ("s" in Options)
- if ((StartDate = date2day(Options["s"])) == -1)
- ErrExit(Options["s"] ": Bad date. Usage: -s yy/mm/dd",1)
- if ("e" in Options)
- if ((EndDate = date2day(Options["e"])) == -1)
- ErrExit(Options["e"] ": Bad date. Usage: -e yy/mm/dd",1)
- if ("t" in Options || "T" in Options)
- HeadTailInit()
- if ("M" in Options)
- MinReptBal = Options["M"]
- Verbose = "V" in Options
- Debug = "x" in Options
- if (Debug) {
- printf "Payment types:" > "/dev/stderr"
- for (i in PTypes)
- printf " " i > "/dev/stderr"
- print "" > "/dev/stderr"
- }
- if (ARGC < 2) {
- ARGV[1] = DefExp
- ARGC = 2
- }
- FS = "\t+"
- MaxName = 10
- for (ArgInd = 1; ArgInd < ARGC; ArgInd++) {
- FILENAME = ARGV[ArgInd]
- FNR = 0
- while ((ret = (getline < FILENAME)) == 1) {
- FNR++
- if (Debug)
- printf "** Line %d: %s\n",FNR,$0 > "/dev/stderr"
- if ($0 ~ "^(#|[ \t]*$)")
- continue
- if ($1 ~ /^[a-zA-Z0-9]+=/) # Alias line w/o date
- AliasLine(0,PrintRunning)
- else {
- UnixDays = GetDays($1)
- if (UnixDays > LastDate)
- LastDate = UnixDays
- if (!FirstDate || UnixDays < FirstDate)
- FirstDate = UnixDays
- if ($2 ~ /^[a-zA-Z0-9]+=/) { # Alias line w/ date
- $1 = ""
- AliasLine(UnixDays,PrintRunning)
- }
- else if ($2 == "Rates")
- RatesLine()
- else
- DataLine(UnixDays,Carryover,PrintRunning,RunningBalance,
- LastPayment,PartyListGiven,ProcParties,StartDate,EndDate,
- MaxName,PTypesGiven,PTypes)
- }
- }
- if (ret)
- ErrExit("Error reading file '" FILENAME "'. Exiting.",1)
- }
- # Get time beforehand so that each strftime() will get the same time
- # t = systime()
- # CurMonth = (strftime("%Y",t)-1970)*12+strftime("%m",t)-1
- # CurDay = month2day(CurMonth) + strftime("%d")
-
- if (Carryover)
- ShowCarry()
- else if (Verbose) {
- # Give AllAll the final date + 1 so that it will add the span up to
- # and including the final date
- AddAll(InmateSet,LastAll,LastDate+1)
- VerboseReport(MaxName,PartyListGiven,ProcParties)
- }
- else
- Report("b" in Options,MaxName)
- if (LastPayment)
- ReportLastPayments(Inmates,Coord,ToMixed,LastPay,LastAmt)
- TailFlush()
- }
-
- # MakePTypes takes a payment types list as described in the help info and
- # converts it to a set for easy lookup. Some translations are done to give
- # canonical forms:
- # N->bn,tp P->bp,tn pt->tp pb->bp nt->tn nb->bn b->bp,bn t->tp,tn
- # In the canonical form, only letters from [pnbt] are allowed, and if two
- # letters are given, the letter that specifies the "owed field" (b or t) comes
- # first, and the letter that specifies the sign (p or n) comes last. Thus,
- # the only elements allowed to exist in the set are (p,n,bp,tp,bn,tn)
- function MakePTypes(List,PTypes, Elem,i,Type) {
- split(List,Elem,",")
- for (i in Elem) {
- Type = Elem[i]
- # Convert convenience names
- if (Type == "N") {
- PTypes["bn"]
- PTypes["tp"]
- }
- else if (Type == "P") {
- PTypes["bp"]
- PTypes["tn"]
- }
- else if (Type == "b") {
- PTypes["bp"]
- PTypes["bn"]
- }
- else if (Type == "t") {
- PTypes["tp"]
- PTypes["tn"]
- }
- # Canonicalize letter order
- else if (Type ~ /^[pn][tb]$/)
- PTypes[substr(Type,2) substr(Type,1,1)]
- else if (Type ~ /^([pn]|[tb][pn])$/)
- PTypes[Type]
- else
- return Type ": Bad payment type. Use -h for help."
- }
- return ""
- }
-
- function dFmt(Amount) {
- return sprintf("%.2f",Amount)
- }
-
- function ReportLastPayments(Inmates,Coord,ToMixed,LastPay,LastAmt,
- PartyNum,Party) {
- TailPrint("\nLast payments:")
- for (PartyNum in Inmates) {
- Party = Inmates[PartyNum]
- if (Party != Coord)
- TailPrint(sprintf("%-10s %8s %7s",ToMixed[Party],
- Party in LastPay ? day2date(LastPay[Party]) : "NEVER",
- Party in LastAmt ? "$" dFmt(LastAmt[Party]) : ""))
- }
- }
-
- # Rates line format:
- # date Rates rate-type party-name=amount ...
- function RatesLine() {
- ;
- #print "Rates line: " $0
- }
-
- function AddAll(InmateSet,LastAll,UnixDays, Inmate) {
- for (Inmate in InmateSet) {
- if (Debug)
- printf "Adding %d days (%s to %s) to span for %s\n",
- UnixDays - LastAll,day2date(LastAll),day2date(UnixDays),Inmate \
- > "/dev/stderr"
- # Add the span from the last All line through the current day - 1
- PartySpans[Inmate] += UnixDays - LastAll
- }
- }
-
- # Globals: Year, Aliases, Inmates, InmateSet, Balances, Coord, ToMixed,
- # GlobalOwedTo, GlobalOwedBy, FS, DefaultSep
- # AliasLine: process an alias line.
- # If the line assigns a value to Year, the global Year is set to its value.
- # If the line assigns a value to Sep, the field separtor is set to its value.
- # Otherwise, the RHS of the assignment is put into the global Aliases[]
- # with an index of the LHS.
- # If the line assigns a value to All, the global Inmates[] is set to the
- # parties on its RHS (converted to lower case), they are added as indices
- # to the global Balances[], and the global InmateSet is set to them.
- # If the global Coord is not set and the alias is for All, Coord is set to
- # the first party on the RHS.
- # The global ToMixed[] is maintained as a map from the lowercase version
- # of each name (as used internally for indices) to the mixed-case version of
- # each name as it first appears in an All alias.
- # The global LastAll is used to keep track of the last time All was set,
- # for use by the verbose report.
- # Do lots of checking here since alias lines are relatively infrequent.
- function AliasLine(UnixDays,PrintRunning, Fields,Name,Inmate,i) {
- # Get rid of whitespace since it isn't needed/wanted on an alias line and
- # is not obvious
- if ($0 !~ /^[ \t]+sep[ \t]+=/)
- gsub("[ \t]+","")
- if (split($0,Fields,"=") != 2)
- FileErrExit($0 ": bad alias.",1)
- Name = tolower(Fields[1])
- if (Name == "year") {
- if ((Year = Fields[2]) !~ /^[0-9]+$/)
- FileErrExit("Bad year.",1)
- }
- else if (Name == "owedto") {
- if (Fields[2] != "")
- ExpandList(Fields[2]) # Catch bad party here
- GlobalOwedTo = Fields[2]
- }
- else if (Name == "owedby") {
- if (Fields[2] != "")
- ExpandList(Fields[2]) # Catch bad party here
- GlobalOwedBy = Fields[2]
- }
- else if (Name == "sep") {
- FS = Fields[2]
- DefaultSep = 0
- }
- else {
- if (Name == "")
- FileErrExit("Empty alias name.",1)
- # Allow empty aliases, so that e.g. All can be set to nothing to
- # skip a period of time (relevant to the verbose option)
- if (Fields[2] !~ /^([^-,]+,)?[^-,]+$|/)
- FileErrExit("Bad value assigned to alias.",1)
- if (Debug)
- printf "Adding alias: %s=%s\n",Name,Fields[2] > "/dev/stderr"
- Aliases[Name] = Fields[2]
- if (Name == "all") {
- if (Verbose) {
- if (!UnixDays)
- FileErrExit(\
- "\n'All' alias lines must include a date to use the verbose option",1)
- if (LastAll)
- AddAll(InmateSet,LastAll,UnixDays)
- LastAll = UnixDays
- }
- split(Fields[2],Inmates,",")
- split("",InmateSet)
- for (i in Inmates) {
- Inmate = tolower(Inmates[i])
- if (Debug && Inmate !~ /^[a-z][a-z0-9_]+$/)
- print "Funny inmate name: " Inmate > "/dev/stderr"
- Balances[Inmate]
- InmateSet[Inmate]
- if (!(Inmate in ToMixed))
- ToMixed[Inmate] = Inmates[i]
- Inmates[i] = Inmate
- }
- if (Coord == "")
- Coord = Inmates[1]
- }
- else
- ExpandList(Fields[2]) # Catch any bad parties here
- }
- if (PrintRunning)
- TailPrint($0)
- }
-
- # DataLine: process a data line.
- # Globals: Header, NF, RealTotal, Coord, InmateSet,
- # LastPay, Year, Debug, GlobalOwedBy, GlobalOwedTo
- # 93/03/06 Made ByLines and ToLines global to work around gawk bug
- function DataLine(Date,Carryover,PrintRunning,RunningBalance,LastPayment,
- PartyListGiven,ProcParties,StartDate,EndDate,MaxName,PTypesGiven,PTypes,
- FracExp,OwedTo,Cost,OwedBy,NumLines,LineNum,FieldOffset,
- BalanceLine,FieldsWanted,Format,Party,b,NumOwedByParties,PartyFrac,
- NumOwedToParties,CostChar,GoodParties) {
- if (PrintRunning)
- Format = "%-8s %7s %7s %-" MaxName" s %-" MaxName "s %s"
- if (!Header) {
- # Print header after initial alias lines
- if (PrintRunning)
- TailPrint(sprintf(Format,"Date","Amount","Fr.Amt","Owed-By",
- "Owed-To","Expense-Description"))
- Header = 1
- }
- # Default format is: date<ws>amount<ws>owed-by[<ws>owed-to]<tab>comment
- # <ws> is tab or space. Only the comment field can contain spaces within
- # it. The separator immediately before the comment field must be a tab so
- # that spaces in the comment won't cause the first word of the comment
- # field to be interpreted as the optional owed-to field.
- # Replace with tabs the separators before the comment field that include
- # spaces.
- if (GlobalOwedTo != "") {
- FieldsWanted = GlobalOwedBy == "" ? 4 : 3
- while (NF < FieldsWanted && sub("[ \t]* [ \t]*","\t",$0))
- ;
- }
- else if (DefaultSep) {
- if ($0 !~ /\t/)
- FileErrExit(\
- "\nBad record: must include a tab before the comment field.",1)
- while ($0 ~ / .*\t/) # While there are still spaces before last tab
- sub("[ \t]* [ \t]*","\t",$0)
- }
- Cost = $2
- if (StartDate && Date < StartDate || EndDate && Date > EndDate)
- return
-
- if (GlobalOwedBy != "") {
- OwedBy = GlobalOwedBy
- FieldOffset++
- }
- else
- OwedBy = $3
- if (GlobalOwedTo != "") {
- OwedTo = GlobalOwedTo
- FieldOffset++
- }
- else if (NF == (4-FieldOffset))
- OwedTo = ToMixed[Coord]
- else if (NF == (5-FieldOffset))
- OwedTo = $(4-FieldOffset)
- if (NF != (4-FieldOffset) && NF != (5-FieldOffset))
- FileErrExit("Bad record: " NF " fields.",1)
-
- if (!Cost)
- FileErrExit("\nBad record: 0 cost.",1)
- # This deals with the p & n payment types.
- if (PTypesGiven) {
- # Cost will never be 0
- CostChar = Cost > 0 ? "p" : "n"
- if (CostChar in PTypes)
- PTypesGiven = 0 # Got match; no need for further testing
- }
- if (Debug)
- printf "Cost [pn] type is %s\n",CostChar > "/dev/stderr"
-
- NumOwedByParties = NetPartyList(OwedBy,1,PartyFrac)
- FracExp = Cost / NumOwedByParties
- NumOwedToParties = NetPartyList(OwedTo,-1,PartyFrac)
- if (PTypesGiven) {
- for (Party in ProcParties)
- # party fraction will never be 0
- if (Party in PartyFrac &&
- ((PartyFrac[Party] < 0 ? "t" : "b") CostChar) in PTypes) {
- PTypesGiven = 0
- break
- }
- if (Debug)
- printf "%s[bt] types match.\n",PTypesGiven ? "No " : "" \
- > "/dev/stderr"
- if (PTypesGiven) # No match
- return
- }
-
- # For each party in PartyFrac[], multiply Cost by PartyFrac[party]
- # and add it to Balances[] for that party.
- for (Party in PartyFrac)
- if (!PartyListGiven || Party in ProcParties) {
- GoodParties = 1
- # This line is the only one that does $ transfers in this program!
- Balances[Party] += Cost * PartyFrac[Party]
- if (Verbose)
- VBalances[Party,sign(PartyFrac[Party]) "," sign(Cost)] += \
- abs(Cost * PartyFrac[Party])
- if (Debug)
- printf "PartyFrac[%s]: %s\n",ToMixed[Party],PartyFrac[Party] \
- > "/dev/stderr"
- }
- # Don't report expenses not applied to any we're counting
- if (!GoodParties)
- return
-
- RealTotal += abs(Cost)
- if (PrintRunning) {
- # Clear ByLines and ToLines since they are global for gawk
- split("",ByLines," ")
- split("",ToLines," ")
- # Print this line over however many lines is neccessary for the
- # owed-by and owed-to data to fit in the given field width.
- NumLines = max(SplitLine(OwedBy,ByLines,MaxName),
- SplitLine(OwedTo,ToLines,MaxName))
- TailPrint(sprintf(Format,day2date(Date),sprintf("%7.2f",Cost),
- sprintf("%7.2f",FracExp),ByLines[1],ToLines[1],$NF))
- for (LineNum = 2; LineNum <= NumLines; LineNum++)
- TailPrint(sprintf(Format,"","","",ByLines[LineNum],
- ToLines[LineNum],""))
- if (RunningBalance) {
- for (Inmate in Balances)
- if (Inmate != Coord &&
- (Balance = dFmt(Balances[Inmate])) + 0 != 0 ||
- Inmate in InmateSet)
- BalanceLine = BalanceLine " " ToMixed[Inmate] "=" Balance
- if (BalanceLine != "")
- TailPrint(substr(BalanceLine,2))
- }
- }
- OwedBy = tolower(OwedBy)
- OwedTo = tolower(OwedTo)
- if (Debug)
- printf "Owed to: %s Coord: %s OwedBy: %s Cost: %d\n",OwedTo,Coord,
- OwedBy,Cost > "/dev/stderr"
- # Track date & amount of last payments. This is a payment if it is a
- # negative expense applied by a single party to the coordinator.
- if (LastPayment && OwedTo == Coord && OwedBy in Balances && Cost < 0) {
- if (Debug)
- print "This is a payment line." > "/dev/stderr"
- if (!(OwedBy in LastPay) || Date > LastPay[OwedBy]) {
- LastPay[OwedBy] = Date
- LastAmt[OwedBy] = -Cost
- if (Debug)
- printf "Last payment by %s: %s\n",OwedBy,day2date(Date) \
- > "/dev/stderr"
- }
- }
- }
-
- # Globals: Year
- function GetDays(Date, NElem,EYear,EMonth,EDay,DateElem) {
- NElem = split(Date,DateElem,"/")
- if (NElem == 3) {
- EYear = DateElem[1]
- if (EYear < 100)
- EYear += 1900
- EMonth = DateElem[2]
- EDay = DateElem[3]
- }
- else if (NElem == 2) {
- if (!Year)
- ErrExit(\
- "Year not set in datafile. Must be set to use -l or -d options.\n",1)
- EYear = Year
- EMonth = DateElem[1]
- EDay = DateElem[2]
- }
- else
- FileErrExit("Bad date.",1)
- return YMD2day(EYear,EMonth,EDay)
- }
-
- # Uses globals:
- # VBalances[Party,dir-sign "," cost-sign]
- # dir-sign is 1 for expenses in which Party represents more
- # of the owed-by field than the owed-to field, and -1 for expenses in which
- # Party represents more of the owed-to field than than the owed by field.
- # cost-sign is 1 for expenses in which the cost was positive, and -1 for
- # expenses in which the cost was negative.
- function VerboseReport(MaxName,PartyListGiven,ProcParties,
- Format,DaySpan,YearSpan,Elem,Party,Num) {
- DaySpan = LastDate - FirstDate + 1
- YearSpan = DaySpan / 365.25
- TailPrint(sprintf("First date: %s Last date: %s",
- day2date(FirstDate),day2date(LastDate)))
- TailPrint(sprintf("Period: %d days %.1f weeks %.2f months %.3f years",
- DaySpan,DaySpan/7,YearSpan*12,YearSpan))
- TailPrint("")
- Format = "%-18s %8s %8s %8s %8s %8s"
- TailPrint(sprintf(Format,"Account","Balance","Days","Weeks","Months",
- "Years"))
-
- Num = split(\
- "owed-by+1,-1+1,1|owed-to+-1,-1+-1,1|negative+-1,-1+1,-1|positive+-1,1+1,1|"\
- "owed-by negative+1,-1|owed-by positive+1,1|owed-to negative+-1,-1|"\
- "owed-to positive+-1,1|debits+1,1+-1,-1|credits+1,-1+-1,1|"\
- "transfers+-1,-1+-1,1+1,-1+1,1",Elem,"|")
- for (Party in Balances) {
- if (!PartyListGiven || Party in ProcParties) {
- VPartyReport(Party,Elem,Num,Format)
- if (!PartyListGiven || NumPartiesGiven > 1) {
- VBalances["All","-1,-1"] += VBalances[Party,"-1,-1"]
- VBalances["All","-1,1"] += VBalances[Party,"-1,1"]
- VBalances["All","1,-1"] += VBalances[Party,"1,-1"]
- VBalances["All","1,1"] += VBalances[Party,"1,1"]
- TailPrint("")
- }
- }
- }
- if (!PartyListGiven || NumPartiesGiven > 1) {
- ToMixed["All"] = "All"
- PartySpans["All"] = DaySpan
- VPartyReport("All",Elem,Num,Format)
- }
- }
-
- function VPartyReport(Party,Elem,Num,Format,
- i,E2,Indices,Amt,j,Daily,DaySpan) {
- DaySpan = PartySpans[Party]
- TailPrint(sprintf(Format,ToMixed[Party],dFmt(Balances[Party]),
- sprintf("%d",DaySpan),
- sprintf("%.1f",DaySpan/7),sprintf("%.2f",DaySpan/365.25*12),
- sprintf("%.3f",DaySpan/365.25)))
- TailPrint(sprintf(Format," Expense type","$total","$/day","$/week",
- "$/month","$/year"))
- for (i = 1; i < Num; i++) {
- Indices = split(Elem[i],E2,"+")
- Amt = 0
- for (j = 2; j <= Indices; j++) {
- if (Debug)
- printf "Adding VBalances[%s,%s] (%f) to %s\n",
- Party,E2[j],VBalances[Party,E2[j]],E2[1] > "/dev/stderr"
- Amt += VBalances[Party,E2[j]]
- }
- Daily = Amt/DaySpan
- TailPrint(sprintf(Format," " E2[1],dFmt(Amt),dFmt(Daily),
- dFmt(Daily*7),dFmt(Daily*365.25/12),dFmt(Daily*365.25)))
- }
- }
-
- # Globals: Balances, Coord, RealTotal, InmateSet, ToMixed
- function Report(Brief,MaxName, Inmate,Date,Sum,Balance) {
- Date = strftime("%a %h %d 19%y")
- if (!Brief)
- TailPrint(sprintf("\n***** Balances due to %s as of %s *****",
- ToMixed[Coord],Date))
- for (Inmate in Balances) {
- if (Inmate != Coord) {
- Sum += Balance = dFmt(Balances[Inmate])
- # Report only on those who are currently in the 'All' group,
- # or whose balance is at least a full 2c. Exclude coordinator.
- # Use sprintf'd balance because it is rounded to nearest cent.
- if (abs(Balance) >= MinReptBal || Inmate in InmateSet) {
- if (Brief)
- TailPrint(sprintf("%-" MaxName "s %8s",ToMixed[Inmate],
- Balance))
- else
- TailPrint(sprintf("%-" MaxName "s %8s",ToMixed[Inmate],
- "$" Balance))
- }
- }
- else if (Debug) {
- printf "Excluded: %s. Balance = %f, Inmates[] = %d.\n",
- ToMixed[Inmate],Balances[Inmate],Inmate in InmateSet > "/dev/stderr"
- }
- }
- if (!Brief) {
- TailPrint(sprintf("Total transfers: %9s","$" dFmt(RealTotal)))
- TailPrint(sprintf("Sum of Balances: %9s","$" dFmt(Sum)))
- }
- }
-
- # Globals: Balances, Coord, ToMixed, LastDate, MinReptBal
- function ShowCarry( Inmate,Date) {
- Date = day2date(LastDate)
- for (Inmate in Balances)
- if (Inmate != Coord && abs(Balances[Inmate]) >= MinReptBal )
- TailPrint(sprintf("%s\t%.2f\t%s\tCarryover of expenses",
- Date,Balances[Inmate],ToMixed[Inmate]))
- }
-
- # PartyList is a comma-separated list of parties, optionally including a
- # - sign followed by a list of parties to be removed from the first list.
- # Any of the parties may be an alias.
- # Output variables:
- # For each party in the list, the total number of times the party occurs in the
- # list is divided by the total number of parties (including multiples) in the
- # list and the result is multiplied by Mult and added to PartyFrac[party].
- # Parties that occur the same number of times on both sides of the - sign are
- # not added to PartyFrac[] (so no index will be created for them).
- # It is a terminal error for a party count to be reduce *below* 0,
- # or for there to be no parties left after subtraction.
- # The net number of parties in PartyList (after cancellation) is returned.
- function NetPartyList(PartyList,Mult,PartyFrac,
- PartyCount,Minus,Parties,NotParties,NumParties,Party,NotPartyCount) {
- gsub(" ","",PartyList)
- Minus = index(PartyList,"-")
- if (Minus) {
- Parties = substr(PartyList,1,Minus - 1)
- NotParties = substr(PartyList,Minus + 1)
- }
- else
- Parties = PartyList
- NumParties = ExpandList(Parties,PartyCount)
- if (NotParties != "")
- NumParties -= ExpandList(NotParties,NotPartyCount)
- if (!NumParties)
- FileErrExit("No parties.",1)
- for (Party in NotPartyCount) {
- if (!(Party in PartyCount))
- FileErrExit("\nNegative number of party \"" ToMixed[Party] "\".",1)
- if (!(PartyCount[Party] -= NotPartyCount[Party]))
- delete PartyCount[Party]
- }
- Mult /= NumParties
- for (Party in PartyCount)
- PartyFrac[Party] += PartyCount[Party] * Mult
- return NumParties
- }
-
- # Returns a count in PartyCount[] of the number of times each party occurs in
- # List, indexed by party name.
- # List is a comma-separated list of parties. It should contain no whitespace.
- # Parties are recursively expanded using the global alias table Aliases[]
- # before being counted.
- # Alias is not normally passed in the initial call to ExpandList(); it is used
- # by ExpandList() to detect recursive aliases when it calls itself recursively
- # (it is set to the name that was expanded to the List being passed).
- # Globals: Aliases[]
- # Return value: The total number of parties found after alias expansion
- # (including multiple instances of a particular party).
- function ExpandList(List,PartyCount,Alias,
- Num,Party,Parties,PartyInd,LParty) {
- Num = 0
- # delete PartyCount[0]
- split(List,Parties,",")
- # Parties[1..m] now contains the party names to be counted.
- for (PartyInd in Parties) {
- Party = Parties[PartyInd]
- if (Party == "")
- FileErrExit(\
- "\nEmpty party in list (superfluous comma, or whitespace in list?",1)
- LParty = tolower(Party)
- if (LParty == Alias)
- FileErrExit("Recursive alias: " Party "=" List,1)
- if (LParty in Aliases)
- Num += ExpandList(Aliases[LParty],PartyCount,LParty)
- else if (LParty in Balances) {
- if (Debug)
- printf "Incrementing PartyCount[%s]\n",Party > "/dev/stderr"
- PartyCount[LParty]++
- Num++
- }
- else
- FileErrExit(Party ": bad party.",1)
- }
- return Num
- }
-
- # Start of library routines
-
- # Splits Line into lines in Lines with maximum length of MaxLen.
- # Lines are split on a non-alphanum character if possible.
- # The number of lines put in MaxLen is return.
- function SplitLine(Line,Lines,MaxLen, Len,WordChars,LineNum,MaxLine) {
- WordChars = "[a-zA-Z0-9_]"
- LineNum = 0
- while (Len = length(Line)) {
- Lines[++LineNum] = substr(Line,1,MaxLen)
- if (substr(Line,MaxLen,2) ~ "^" WordChars WordChars) {
- MaxLine = Lines[LineNum]
- sub(WordChars "*$","",MaxLine)
- if (MaxLine != "")
- Lines[LineNum] = MaxLine
- }
- Line = substr(Line,length(Lines[LineNum]) + 1)
- }
- return LineNum
- }
-
- function max(a,b) {
- if (a > b)
- return a
- else
- return b
- }
-
- # @(#) headtail.awk 95/06/20
- # 95/04/28 Added tail routines.
-
- # Turn on screen-bounded printing.
- # Sets global vars LINES and COLUMNS.
- # Set either of them to 0 after calling this function if you do not want
- # limiting of lines or line length respectively.
- function HeadTailInit( Cmd) {
- # tput will use values in environment, but we want to avoid running
- # it if possible.
- if ("COLUMNS" in ENVIRON)
- COLUMNS = ENVIRON["COLUMNS"]
- else {
- Cmd = "tput cols"
- Cmd | getline COLUMNS
- close(Cmd)
- if (COLUMNS == "")
- COLUMNS = 80
- }
- if ("LINES" in ENVIRON)
- LINES = ENVIRON["LINES"]
- else {
- Cmd = "tput lines"
- Cmd | getline LINES
- close(Cmd)
- if (LINES == "")
- LINES = 24
- }
- }
-
- # Do screen-bound printing.
- # If LINES is >0, the last LINES-1 lines are kept in a circular buffer.
- # When TailFlush() is called, they are printed.
- # If LINES = 0, all lines are printed immediately.
- # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
- # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
- # saves lines in TailLines[] from 1..LINES-1
- # Embedded newlines split the line into multiple lines; trailing newlines are
- # stripped. Tabs are expanded to spaces.
- function TailPrint(Line) {
- if (!LINES)
- print Line
- else {
- if (++TailPtr > (LINES-1))
- TailPtr = 1
- TailLines[TailPtr] = Line
- }
- }
-
- function TailFlush( NumPrinted,Lines,Line,i,Buffer,PrintLines) {
- if (!LINES)
- return
- NumPrinted = 0
- PrintLines = LINES-1
- # Since lines may contain multiple lines, we must create a buffer to be
- # printed by reading line buffer backwards.
- # Stop when we've copied enough lines, or if we wrap around to the end and
- # find that the entire line buffer wasn't used.
- while (NumPrinted < PrintLines && TailPtr in TailLines) {
- # Split line into individual lines, then process them last to first
- Num = split(TailLines[TailPtr],Lines,"\n")
- for (i = Num; i >= 1; i--) {
- Line = Lines[i]
- if (i == Num && Line == "") # discard trailing newline
- continue
- # Put this line at the front of the print buffer
- if (COLUMNS)
- Buffer = substr(TabEx(Line),1,COLUMNS - 1) "\n" Buffer
- else
- Buffer = Line "\n" Buffer
- if (++NumPrinted == PrintLines)
- break
- }
- if (!--TailPtr) # Wrap pointer if neccessary
- TailPtr = PrintLines
- }
- printf "%s",Buffer
- }
-
- # Do screen-bound printing.
- # If LINES >0, returns 0 when LINES-1 lines have been printed by HeadPrint().
- # Otherwise returns 1.
- # If COLUMNS is >0, truncates Line to COLUMNS-1 characters before printing it.
- # Global vars: uses LINES & COLUMNS; sets/uses LinesPrinted.
- # Line should not include newlines.
- function HeadPrint(Line) {
- # Check first, in case some calls of this function to not check return
- # value, and in case LINES is 1.
- if (LINES && LinesPrinted >= (LINES-1))
- return 0
- if (COLUMNS)
- print substr(Line,1,COLUMNS - 1)
- else
- print Line
- if (LINES && ++LinesPrinted >= (LINES-1))
- return 0
- return 1
- }
-
- # Expand tabs in Line
- function TabEx(Line, Segs,i,Num,S) {
- Num = split(Line,Segs,"\t")
- Line = ""
- for (i = 1; i < Num; i++) {
- S = Segs[i]
- Line = Line S substr(" ",length(S) % 8 + 1)
- }
- return Line Segs[Num]
- }
-
- function LineErr(S) {
- ErrPrint("Error on line " FNR ": " S)
- }
-
- function FileErr(S) {
- ErrPrint("Error on line " FNR " of file \"" FILENAME "\": " S)
- }
-
- function FileErrExit(S,ExitVal) {
- FileErr(S)
- Err = ExitVal
- exit(ExitVal)
- }
-
- function ErrPrint(S) {
- print S > "/dev/stderr"
- }
-
- #function ErrPrint(S) {
- # print S | "cat 1>&2"
- #}
-
- function ErrExit(S,ExitVal) {
- ErrPrint(S)
- Err = ExitVal
- exit(ExitVal)
- }
-
- # MakeSet: make a set from a list.
- # An index with the name of each element of the list
- # is created in the given array.
- # Input variables:
- # Elements is a string containing the list of elements.
- # Sep is the character that separates the elements of the list.
- # Output variables:
- # Set is the array.
- # Return value: the number of elements added to the set.
- function MakeSet(Set,Elements,Sep, i,Num,Names) {
- Num = split(Elements,Names,Sep)
- for (i = 1; i <= Num; i++)
- Set[Names[i]]
- return Num
- }
-
- ### Begin sign routines
-
- function sign(value) {
- if (value > 0)
- return 1
- else if (value < 0)
- return -1
- else
- return 0
- }
-
- function abs(value) {
- if (value+0 >= 0)
- return value
- else
- return -value
- }
-
- ### End sign routines
- ### Begin date-days routines
-
- # YMD2day(year,month,day-of-month) returns the number of days that passed from
- # 1970 Jan 1 to the given date.
- # All parameters should be given in numeric form.
- # If year < 70, it is assumed to be part of the 2000 century
- # If year in (70..99), 1900.
- # Globals: sets and uses MDays[]
- function YMD2day(Year,Month,Day, LeapDays) {
- Year+=0
- Month+=0
- if (Year < 70)
- Year += 100
- else if (Year >= 100)
- Year -= 1900
- # Year is now the number of years since 1900.
- LeapDays = int((Year - 68) / 4)
- if (Month <= 2 && Year % 4 == 0)
- LeapDays -= 1
- if (!(0 in MDays))
- split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
- return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
- }
-
- # date2day("yy/mm/dd") returns the number of days that passed from
- # 1970 Jan 1 to the given date. -1 is returned on error.
- # The fields are returned in Fields: year in Fields[1], month in Fields[2],
- # and day (if given) in Fields[3].
- function date2day(Date,Fields, Num,Year,Month) {
- Num = split(Date,Fields,"/")
- if (Num != 2 && Num != 3)
- return -1
- if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
- return -1
- if (Num == 3)
- Day = Fields[3]
- return YMD2day(Year,Month,Day)
- }
-
- # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
- # returns the number of complete days that passed from date 1 to date 2
- function diffdays(year1,month1,day1,year2,month2,day2) {
- return date2days(year2,month2,day2) - date2days(year1,month1,day1)
- }
-
- # Given an epoch month, return the first day of that month
- function month2day(Month) {
- return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
- }
-
- # Given an epoch day, returns epoch month
- function day2month(Day, Date) {
- day2YMD(Day,Date)
- return (Date["y"]-1970)*12 + Date["m"]-1
- }
-
- # Given an epoch month, returns the number of days in that month.
- function monthdays(month, year) {
- if (!(0 in MDur))
- split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
- year = int(month/12)
- month = month%12+1
- return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
- }
-
- # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.),
- # returns the date elements in Date:
- # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
- # Date["d"] = day of month.
- # Globals: Sets/uses MDays[].
- function day2YMD(Day,Date, QYears,Year,NonLeapYears,Month) {
- if (!(0 in LDays)) {
- split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
- split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
- }
- Day += 365
- # Day is now # of days since Jan 1 1969. 1968 was a leap year.
- QYears = int(Day / (365*4+1))
- Year = 1969 + QYears * 4
- Day -= QYears * (365*4+1)
- # Day now contains no complete leap years.
- Year += NonLeapYears = int(Day/365)
- Leap = !(Year % 4)
- Day -= NonLeapYears * 365
- # Day now contains the day of year.
- # Find the month. Divide day by 32 to get either the correct month or
- # the month prior to it.
- Month = int(Day++ / 32) + 1
- if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
- Month++
- Day -= Leap ? LDays[Month] : MDays[Month]
- Date["d"] = Day
- Date["m"] = Month
- Date["y"] = Year
- }
-
- # Given a month number, return a date in the form yy/mm
- function month2date(MonthNum) {
- return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
- }
-
- # Given a day number, return a date in the form yy/mm/dd
- function day2date(day) {
- day2YMD(day,Date)
- return sprintf("%02d/%02d/%02d",Date["y"]%100,Date["m"],Date["d"])
- }
-
- ### End date-days routines
-
- ### Start of ProcArgs library
- # @(#) ProcArgs 1.11 96/12/08
- # 92/02/29 john h. dubois iii (john@armory.com)
- # 93/07/18 Added "#" arg type
- # 93/09/26 Do not count -h against MinArgs
- # 94/01/01 Stop scanning at first non-option arg. Added ">" option type.
- # Removed meaning of "+" or "-" by itself.
- # 94/03/08 Added & option and *()< option types.
- # 94/04/02 Added NoRCopt to Opts()
- # 94/06/11 Mark numeric variables as such.
- # 94/07/08 Opts(): Do not require any args if h option is given.
- # 95/01/22 Record options given more than once. Record option num in argv.
- # 95/06/08 Added ExclusiveOptions().
- # 96/01/20 Let rcfiles be a colon-separated list of filenames.
- # Expand $VARNAME at the start of its filenames.
- # Let varname=0 and -option- turn off an option.
- # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
- # of the vars should be searched for in the environment.
- # Check for duplicate rcfiles.
- # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts()
- # now return various negatives values on error, not just -1, and
- # Opts() may set Err to various positive values, not just 1.
- # Added AllowUnrecOpt.
- # 96/05/23 Check type given for & option
- # 96/06/15 Re-port to awk
- # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
- # used by other functions.
- # 96/10/15 Added OptChars
- # 96/11/01 Added exOpts arg to Opts()
- # 96/11/16 Added ; type
- # 96/12/08 Added Opt2Set() & Opt2Sets()
- # 96/12/27 Added CmdLineOpt()
-
- # optlist is a string which contains all of the possible command line options.
- # A character followed by certain characters indicates that the option takes
- # an argument, with type as follows:
- # : String argument
- # ; Non-empty string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # The only difference the type of argument makes is in the runtime argument
- # error checking that is done.
-
- # The & option is a special case used to get numeric options without the
- # user having to give an option character. It is shorthand for [-+.0-9].
- # If & is included in optlist and an option string that begins with one of
- # these characters is seen, the value given to "&" will include the first
- # char of the option. & must be followed by a type character other than ":"
- # or ";".
- # Note that if e.g. &> is given, an option of -.5 will produce an error.
-
- # Strings in argv[] which begin with "-" or "+" are taken to be
- # strings of options, except that a string which consists solely of "-"
- # or "+" is taken to be a non-option string; like other non-option strings,
- # it stops the scanning of argv and is left in argv[].
- # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
- # If an option takes an argument, the argument may either immediately
- # follow it or be given separately.
- # "-" and "+" options are treated the same. "+" is allowed because most awks
- # take any -options to be arguments to themselves. gawk 2.15 was enhanced to
- # stop scanning when it encounters an unrecognized option, though until 2.15.5
- # this feature had a flaw that caused problems in some cases. See the OptChars
- # parameter to explicitly set the option-specifier characters.
-
- # If an option that does not take an argument is given,
- # an index with its name is created in Options and its value is set to the
- # number of times it occurs in argv[].
-
- # If an option that does take an argument is given, an index with its name is
- # created in Options and its value is set to the value of the argument given
- # for it, and Options[option-name,"count"] is (initially) set to the 1.
- # If an option that takes an argument is given more than once,
- # Options[option-name,"count"] is incremented, and the value is assigned to
- # the index (option-name,instance) where instance is 2 for the second occurance
- # of the option, etc.
- # In other words, the first time an option with a value is encountered, the
- # value is assigned to an index consisting only of its name; for any further
- # occurances of the option, the value index has an extra (count) dimension.
-
- # The sequence number for each option found in argv[] is stored in
- # Options[option-name,"num",instance], where instance is 1 for the first
- # occurance of the option, etc. The sequence number starts at 1 and is
- # incremented for each option, both those that have a value and those that
- # do not. Options set from a config file have a value of 0 assigned to this.
-
- # Options and their arguments are deleted from argv.
- # Note that this means that there may be gaps left in the indices of argv[].
- # If compress is nonzero, argv[] is packed by moving its elements so that
- # they have contiguous integer indices starting with 0.
- # Option processing will stop with the first unrecognized option, just as
- # though -- was given except that unlike -- the unrecognized option will not be
- # removed from ARGV[]. Normally, an error value is returned in this case.
- # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
- # be found, so the number of remaining arguments is returned instead.
- # If OptChars is not a null string, it is the set of characters that indicate
- # that an argument is an option string if the string begins with one of the
- # characters. A string consisting solely of two of the same option-indicator
- # characters stops the scanning of argv[]. The default is "-+".
- # argv[0] is not examined.
- # The number of arguments left in argc is returned.
- # If an error occurs, the global string OptErr is set to an error message
- # and a negative value is returned.
- # Current error values:
- # -1: option that required an argument did not get it.
- # -2: argument of incorrect type supplied for an option.
- # -3: unrecognized (invalid) option.
- function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
- ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
- NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
- {
- # ArgNum is the index of the argument being processed.
- # ArgsLeft is the number of arguments left in argv.
- # Arg is the argument being processed.
- # ArgLen is the length of the argument being processed.
- # ArgInd is the position of the character in Arg being processed.
- # Option is the character in Arg being processed.
- # Pos is the position in OptList of the option being processed.
- # NumOpt is true if a numeric option may be given.
- ArgsLeft = argc
- NumOpt = index(OptList,"&")
- OptionNum = 0
- if (OptChars == "")
- OptChars = "-+"
- while (OptChars != "") {
- c = substr(OptChars,1,1)
- OptChars = substr(OptChars,2)
- OptCharSet[c]
- OptTerm[c c]
- }
- for (ArgNum = 1; ArgNum < argc; ArgNum++) {
- Arg = argv[ArgNum]
- if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
- break # Not an option; quit
- if (Arg in OptTerm) {
- delete argv[ArgNum]
- ArgsLeft--
- break
- }
- ArgLen = length(Arg)
- for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
- Option = substr(Arg,ArgInd,1)
- if (NumOpt && Option ~ /[-+.0-9]/) {
- # If this option is a numeric option, make its flag be & and
- # its option string flag position be the position of & in
- # the option string.
- Option = "&"
- Pos = NumOpt
- # Prefix Arg with a char so that ArgInd will point to the
- # first char of the numeric option.
- Arg = "&" Arg
- ArgLen++
- }
- # Find position of flag in option string, to get its type (if any).
- # Disallow & as literal flag.
- else if (!(Pos = index(OptList,Option)) || Option == "&") {
- if (AllowUnrecOpt) {
- Escape = 1
- break
- }
- else {
- OptErr = "Invalid option: " specGiven Option
- return -3
- }
- }
-
- # Find what the value of the option will be if it takes one.
- # NeedNextOpt is true if the option specifier is the last char of
- # this arg, which means that if the option requires a value it is
- # the next arg.
- if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
- if (GotValue = ArgNum + 1 < argc)
- Value = argv[ArgNum+1]
- }
- else { # Value is included with option
- Value = substr(Arg,ArgInd + 1)
- GotValue = 1
- }
-
- if (HadValue = AssignVal(Option,Value,Options,
- substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
- specGiven)) {
- if (HadValue < 0) # error occured
- return HadValue
- if (HadValue == 2)
- ArgInd++ # Account for the single-char value we used.
- else {
- if (NeedNextOpt) { # option took next arg as value
- delete argv[++ArgNum]
- ArgsLeft--
- }
- break # This option has been used up
- }
- }
- }
- if (Escape)
- break
- # Do not delete arg until after processing of it, so that if it is not
- # recognized it can be left in ARGV[].
- delete argv[ArgNum]
- ArgsLeft--
- }
- if (compress != 0) {
- dest = 1
- src = argc - ArgsLeft + 1
- for (count = ArgsLeft - 1; count; count--) {
- ARGV[dest] = ARGV[src]
- dest++
- src++
- }
- }
- return ArgsLeft
- }
-
- # Assignment to values in Options[] occurs only in this function.
- # Option: Option specifier character.
- # Value: Value to be assigned to option, if it takes a value.
- # Options[]: Options array to return values in.
- # ArgType: Argument type specifier character.
- # GotValue: Whether any value is available to be assigned to this option.
- # Name: Name of option being processed.
- # OptionNum: Number of this option (starting with 1) if set in argv[],
- # or 0 if it was given in a config file or in the environment.
- # SingleOpt: true if the value (if any) that is available for this option was
- # given as part of the same command line arg as the option. Used only for
- # options from the command line.
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Global variables: OptErr
- # Return value: negative value on error, 0 if option did not require an
- # argument, 1 if it did & used the whole arg, 2 if it required just one char of
- # the arg.
- # Current error values:
- # -1: Option that required an argument did not get it.
- # -2: Value of incorrect type supplied for option.
- # -3: Bad type given for option &
- function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
- SingleOpt,specGiven, UsedValue,Err,NumTypes) {
- # If option takes a value... [
- NumTypes = "*()#<>]"
- if (Option == "&" && ArgType !~ "[" NumTypes) { # ]
- OptErr = "Bad type given for & option"
- return -3
- }
-
- if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ]
- if (!GotValue) {
- if (Name != "")
- OptErr = "Variable requires a value -- " Name
- else
- OptErr = "option requires an argument -- " Option
- return -1
- }
- if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
- OptErr = Err
- return -2
- }
- # Mark this as a numeric variable; will be propogated to Options[] val.
- if (ArgType != ":" && ArgType != ";")
- Value += 0
- if ((Instance = ++Options[Option,"count"]) > 1)
- Options[Option,Instance] = Value
- else
- Options[Option] = Value
- }
- # If this is an environ or rcfile assignment & it was given a value...
- else if (!OptionNum && Value != "") {
- UsedValue = 1
- # If the value is "0" or "-" and this is the first instance of it,
- # do not set Options[Option]; this allows an assignment in an rcfile to
- # turn off an option (for the simple "Option in Options" test) in such
- # a way that it cannot be turned on in a later file.
- if (!(Option in Options) && (Value == "0" || Value == "-"))
- Instance = 1
- else
- Instance = ++Options[Option]
- # Save the value even though this is a flag
- Options[Option,Instance] = Value
- }
- # If this is a command line flag and has a - following it in the same arg,
- # it is being turned off.
- else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
- UsedValue = 2
- if (Option in Options)
- Instance = ++Options[Option]
- else
- Instance = 1
- Options[Option,Instance]
- }
- # If this is a flag assignment without a value, increment the count for the
- # flag unless it was turned off. The indicator for a flag being turned off
- # is that the flag index has not been set in Options[] but it has an
- # instance count.
- else if (Option in Options || !((Option,1) in Options))
- # Increment number of times this flag seen; will inc null value to 1
- Instance = ++Options[Option]
- Options[Option,"num",Instance] = OptionNum
- return UsedValue
- }
-
- # Option is the option letter
- # Value is the value being assigned
- # Name is the var name of the option, if any
- # ArgType is one of:
- # : String argument
- # ; Non-null string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Returns null on success, err string on error
- function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) {
- if (ArgType == ":")
- return ""
- if (ArgType == ";") {
- if (Value == "")
- Err = "must be a non-empty string"
- }
- # A number begins with optional + or -, and is followed by a string of
- # digits or a decimal with digits before it, after it, or both
- else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
- Err = "must be a number"
- else if (ArgType ~ "[#<>]" && Value ~ /\./)
- Err = "may not include a fraction"
- else if (ArgType ~ "[()<>]" && Value < 0)
- Err = "may not be negative"
- # (
- else if (ArgType ~ "[)>]" && Value == 0)
- Err = "must be a positive number"
- if (Err != "") {
- ErrStr = "Bad value \"" Value "\". Value assigned to "
- if (Name != "")
- return ErrStr "variable " substr(Name,1,1) " " Err
- else {
- if (Option == "&")
- Option = Value
- return ErrStr "option " specGiven substr(Option,1,1) " " Err
- }
- }
- else
- return ""
- }
-
- # Note: only the above functions are needed by ProcArgs.
- # The rest of these functions call ProcArgs() and also do other
- # option-processing stuff.
-
- # Opts: Process command line arguments.
- # Opts processes command line arguments using ProcArgs()
- # and checks for errors. If an error occurs, a message is printed
- # and the program is exited.
- #
- # Input variables:
- # Name is the name of the program, for error messages.
- # Usage is a usage message, for error messages.
- # OptList the option description string, as used by ProcArgs().
- # MinArgs is the minimum number of non-option arguments that this
- # program should have, non including ARGV[0] and +h.
- # If the program does not require any non-option arguments,
- # MinArgs should be omitted or given as 0.
- # rcFiles, if given, is a colon-seprated list of filenames to read for
- # variable initialization. If a filename begins with ~/, the ~ is replaced
- # by the value of the environment variable HOME. If a filename begins with
- # $, the part from the character after the $ up until (but not including)
- # the first character not in [a-zA-Z0-9_] will be searched for in the
- # environment; if found its value will be substituted, if not the filename will
- # be discarded.
- # rcfiles are read in the order given.
- # Values given in them will not override values given on the command line,
- # and values given in later files will not override those set in earlier
- # files, because AssignVal() will store each with a different instance index.
- # The first instance of each variable, either on the command line or in an
- # rcfile, will be stored with no instance index, and this is the value
- # normally used by programs that call this function.
- # VarNames is a comma-separated list of variable names to map to options,
- # in the same order as the options are given in OptList.
- # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
- # searched for in the environment. If set to -1, all values will be searched
- # for in the environment. Values given in the environment will override
- # those given in the rcfiles but not those given on the command line.
- # NoRCopt, if given, is an additional letter option that if given on the
- # command line prevents the rcfiles from being read.
- # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
- # ExclusiveOptions() for a description of exOpts.
- # Special options:
- # If x is made an option and is given, some debugging info is output.
- # h is assumed to be the help option.
-
- # Global variables:
- # The command line arguments are taken from ARGV[].
- # The arguments that are option specifiers and values are removed from
- # ARGV[], leaving only ARGV[0] and the non-option arguments.
- # The number of elements in ARGV[] should be in ARGC.
- # After processing, ARGC is set to the number of elements left in ARGV[].
- # The option values are put in Options[].
- # On error, Err is set to a positive integer value so it can be checked for in
- # an END block.
- # Return value: The number of elements left in ARGV is returned.
- # Must keep OptErr global since it may be set by InitOpts().
- function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
- AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) {
- if (MinArgs == "")
- MinArgs = 0
- ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
- optChars)
- if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
- if (ArgsLeft >= 0) {
- OptErr = "Not enough arguments"
- Err = 4
- }
- else
- Err = -ArgsLeft
- printf "%s: %s.\nUse -h for help.\n%s\n",
- Name,OptErr,Usage > "/dev/stderr"
- exit 1
- }
- if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
- (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
- {
- print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
- Err = -e
- exit 1
- }
- if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
- {
- printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
- Err = 1
- exit 1
- }
- return ArgsLeft
- }
-
- # ReadConfFile(): Read a file containing var/value assignments, in the form
- # <variable-name><assignment-char><value>.
- # Whitespace (spaces and tabs) around a variable (leading whitespace on the
- # line and whitespace between the variable name and the assignment character)
- # is stripped. Lines that do not contain an assignment operator or which
- # contain a null variable name are ignored, other than possibly being noted in
- # the return value. If more than one assignment is made to a variable, the
- # first assignment is used.
- # Input variables:
- # File is the file to read.
- # Comment is the line-comment character. If it is found as the first non-
- # whitespace character on a line, the line is ignored.
- # Assign is the assignment string. The first instance of Assign on a line
- # separates the variable name from its value.
- # If StripWhite is true, whitespace around the value (whitespace between the
- # assignment char and trailing whitespace on the line) is stripped.
- # VarPat is a pattern that variable names must match.
- # Example: "^[a-zA-Z][a-zA-Z0-9]+$"
- # If FlagsOK is true, variables are allowed to be "set" by being put alone on
- # a line; no assignment operator is needed. These variables are set in
- # the output array with a null value. Lines containing nothing but
- # whitespace are still ignored.
- # Output variables:
- # Values[] contains the assignments, with the indexes being the variable names
- # and the values being the assigned values.
- # Lines[] contains the line number that each variable occured on. A flag set
- # is record by giving it an index in Lines[] but not in Values[].
- # Return value:
- # If any errors occur, a string consisting of descriptions of the errors
- # separated by newlines is returned. In no case will the string start with a
- # numeric value. If no errors occur, the number of lines read is returned.
- function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
- FlagsOK,
- Line,Status,Errs,AssignLen,LineNum,Var,Val) {
- if (Comment != "")
- Comment = "^" Comment
- AssignLen = length(Assign)
- if (VarPat == "")
- VarPat = "." # null varname not allowed
- while ((Status = (getline Line < File)) == 1) {
- LineNum++
- sub("^[ \t]+","",Line)
- if (Line == "") # blank line
- continue
- if (Comment != "" && Line ~ Comment)
- continue
- if (Pos = index(Line,Assign)) {
- Var = substr(Line,1,Pos-1)
- Val = substr(Line,Pos+AssignLen)
- if (StripWhite) {
- sub("^[ \t]+","",Val)
- sub("[ \t]+$","",Val)
- }
- }
- else {
- Var = Line # If no value, var is entire line
- Val = ""
- }
- if (!FlagsOK && Val == "") {
- Errs = Errs \
- sprintf("\nBad assignment on line %d of file %s: %s",
- LineNum,File,Line)
- continue
- }
- sub("[ \t]+$","",Var)
- if (Var !~ VarPat) {
- Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
- LineNum,File,Var)
- continue
- }
- if (!(Var in Lines)) {
- Lines[Var] = LineNum
- if (Pos)
- Values[Var] = Val
- }
- }
- if (Status)
- Errs = Errs "\nCould not read file " File
- close(File)
- return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline
- }
-
- # Variables:
- # Data is stored in Options[].
- # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
- # Global vars:
- # Sets OptErr. Uses ENVIRON[].
- # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
- function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
- Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
- fNames,numrcFiles,filesRead,Err,Values,retStr) {
- split("",filesRead,"") # make awk know this is an array
- NumVars = split(VarNames,Vars,",")
- TypesInd = Ret = 0
- if (EnvSearch == -1)
- EnvSearch = NumVars
- for (i = 1; i <= NumVars; i++) {
- Var = Vars[i]
- CharOpt = substr(OptList,++TypesInd,1)
- if (CharOpt ~ "^[:;*()#<>&]$")
- CharOpt = substr(OptList,++TypesInd,1)
- Map[Var] = CharOpt
- Types[Var] = Type = substr(OptList,TypesInd+1,1)
- # Do not overwrite entries from environment
- if (i <= EnvSearch && Var in ENVIRON &&
- (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
- return Err
- }
-
- numrcFiles = split(rcFiles,fNames,":")
- for (i = 1; i <= numrcFiles; i++) {
- rcFile = fNames[i]
- if (rcFile ~ "^~/")
- rcFile = ENVIRON["HOME"] substr(rcFile,2)
- else if (rcFile ~ /^\$/) {
- rcFile = substr(rcFile,2)
- match(rcFile,"^[a-zA-Z0-9_]*")
- envvar = substr(rcFile,1,RLENGTH)
- if (envvar in ENVIRON)
- rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
- else
- continue
- }
- if (rcFile in filesRead)
- continue
- # rcfiles are liable to be given more than once, e.g. UHOME and HOME
- # may be the same
- filesRead[rcFile]
- if ("x" in Options)
- printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
- retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
- if (retStr > 0)
- READ_RCFILE = 1
- else if (ret != "") {
- OptErr = retStr
- Ret = -1
- }
- for (Var in Lines)
- if (Var in Map) {
- if ((Err = AssignVal(Map[Var],
- Var in Values ? Values[Var] : "",Options,Types[Var],
- Var in Values,Var,0)) < 0)
- return Err
- }
- else {
- OptErr = sprintf(\
- "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
- Lines[Var],rcFile)
- Ret = -1
- }
- }
-
- if ("x" in Options)
- for (Var in Map)
- if (Map[Var] in Options)
- printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
- "/dev/stderr"
- else
- printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
- return Ret
- }
-
- # OptSets is a semicolon-separated list of sets of option sets.
- # Within a list of option sets, the option sets are separated by commas. For
- # each set of sets, if any option in one of the sets is in Options[] AND any
- # option in one of the other sets is in Options[], an error string is returned.
- # If no conflicts are found, nothing is returned.
- # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
- # the exclusions presented by the first set of sets (ab,def,g) if:
- # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
- # (a or b is in Options[]) AND (g is in Options) OR
- # (d, e, or f is in Options[]) AND (g is in Options)
- # An error will be returned due to the exclusions presented by the second set
- # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
- # todo: make options given on command line unset options given in config file
- # todo: that they conflict with.
- function ExclusiveOptions(OptSets,Options,
- Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
- SetNum,OSetNum) {
- NumSetSets = split(OptSets,SetSets,";")
- # For each set of sets...
- for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
- # NumSets is the number of sets in this set of sets.
- NumSets = split(SetSets[SetSet],Sets,",")
- # For each set in a set of sets except the last...
- for (SetNum = 1; SetNum < NumSets; SetNum++) {
- s1 = Sets[SetNum]
- L1 = length(s1)
- for (Pos1 = 1; Pos1 <= L1; Pos1++)
- # If any of the options in this set was given, check whether
- # any of the options in the other sets was given. Only check
- # later sets since earlier sets will have already been checked
- # against this set.
- if ((c1 = substr(s1,Pos1,1)) in Options)
- for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
- s2 = Sets[OSetNum]
- L2 = length(s2)
- for (Pos2 = 1; Pos2 <= L2; Pos2++)
- if ((c2 = substr(s2,Pos2,1)) in Options)
- ErrStr = ErrStr "\n"\
- sprintf("Cannot give both %s and %s options.",
- c1,c2)
- }
- }
- }
- if (ErrStr != "")
- return substr(ErrStr,2)
- return ""
- }
-
- # The value of each instance of option Opt that occurs in Options[] is made an
- # index of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Set(Options,Opt,Set, count) {
- if (!(Opt in Options))
- return 0
- Set[Options[Opt]]
- count = Options[Opt,"count"]
- for (; count > 1; count--)
- Set[Options[Opt,count]]
- return count
- }
-
- # The value of each instance of option Opt that occurs in Options[] that
- # begins with "!" is made an index of nSet[] (with the ! stripped from it).
- # Other values are made indexes of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) {
- ret = Opt2Set(Options,Opt,aSet)
- for (value in aSet)
- if (substr(value,1,1) == "!")
- nSet[substr(value,2)]
- else
- Set[value]
- return ret
- }
-
- # Returns true if option Opt was given on the command line.
- function CmdLineOpt(Options,Opt, i) {
- for (i = 1; (Opt,"num",i) in Options; i++)
- if (Options[Opt,"num",i] != 0)
- return 1
- return 0
- }
- ### End of ProcArgs library
-